[client-v2,jdbc-v2] Change credentials in realtime#2812
[client-v2,jdbc-v2] Change credentials in realtime#2812
Conversation
Client V2 CoverageCoverage Report
Class Coverage
|
JDBC V2 CoverageCoverage Report
Class Coverage
|
JDBC V1 CoverageCoverage Report
Class Coverage
|
Client V1 CoverageCoverage Report
Class Coverage
|
|
There was a problem hiding this comment.
Pull request overview
Adds runtime credential update support across client-v2 and aligns jdbc-v2 with the newer access-token configuration key, along with new examples and tests to demonstrate/authenticate the behavior.
Changes:
- Introduces a
CredentialsManagerand newClientruntime APIs (setCredentials(),setAccessToken()) to update auth for subsequent requests. - Renames/aliases bearer-token config to preferred
access_tokenand updates JDBC parsing/tests accordingly. - Adds new client-v2 and JDBC examples plus additional integration/unit tests for credential updates.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
client-v2/src/main/java/com/clickhouse/client/api/Client.java |
Adds runtime auth update APIs and routes request settings through CredentialsManager. |
client-v2/src/main/java/com/clickhouse/client/api/internal/CredentialsManager.java |
New component to manage mutable credentials and validate auth config. |
client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java |
Adds ACCESS_TOKEN and documents legacy BEARERTOKEN_AUTH alias. |
client-v2/src/test/java/com/clickhouse/client/api/internal/CredentialsManagerTest.java |
Unit tests for auth validation and credential snapshot/apply behavior. |
client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java |
Updates token-auth tests to setAccessToken() and adds integration test for setCredentials(). |
client-v2/src/test/java/com/clickhouse/client/ClientTests.java |
Adds integration test switching users via setCredentials(). |
jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java |
Accepts access_token with fallback to legacy bearer token property. |
jdbc-v2/src/main/java/com/clickhouse/jdbc/DataSourceImpl.java |
Uses ClientConfigProperties keys and clears token/SSL-auth when connecting with username/password. |
jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java |
Updates integration test to use ACCESS_TOKEN property and renames test. |
examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/Authentication.java |
New example demonstrating runtime auth updates on an existing client. |
examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/RuntimeCredentialsTwoUsers.java |
New demo creating users and switching credentials at runtime. |
examples/client-v2/README.md |
Documents the runtime credentials switch demo usage. |
examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/Authentication.java |
New JDBC example showing auth changes by reconnecting with new properties. |
docs/features.md |
Documents runtime credential updates as stable behavior/compat trait. |
| public void applyCredentials(Map<String, Object> target) { | ||
| putIfNotNull(target, ClientConfigProperties.USER.getKey(), username); | ||
| putIfNotNull(target, ClientConfigProperties.PASSWORD.getKey(), password); | ||
| putIfNotNull(target, ClientConfigProperties.ACCESS_TOKEN.getKey(), accessToken); | ||
| putIfNotNull(target, AUTHORIZATION_HEADER_KEY, authorizationHeader); | ||
| if (useSslAuth) { | ||
| target.put(ClientConfigProperties.SSL_AUTH.getKey(), Boolean.TRUE); | ||
| } |
There was a problem hiding this comment.
applyCredentials() only adds non-null credential fields but never removes stale authentication keys already present in target. Since Client.buildRequestSettings() starts from a copy of Client.configuration, switching from access token (which sets http_header_AUTHORIZATION) back to username/password via setCredentials() will keep sending the old Bearer Authorization header and ignore the new credentials. Consider explicitly removing all auth-related entries (USER, PASSWORD, ACCESS_TOKEN, AUTHORIZATION header key, SSL_AUTH) from target before applying the current mode, or removing these keys from the base Client.configuration after initializing CredentialsManager so requests always rely on the manager snapshot.
| this.configuration.put(ClientConfigProperties.ACCESS_TOKEN.getKey(), accessToken); | ||
| this.configuration.remove(ClientConfigProperties.BEARERTOKEN_AUTH.getKey()); | ||
| this.httpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); |
There was a problem hiding this comment.
setAccessToken() always writes an Authorization: Bearer ... header even when accessToken is null/blank, which can result in sending Bearer null (or Bearer ) on requests. It would be safer to validate the token (non-null/non-blank) and otherwise remove/clear both the access_token config entry and the Authorization header entry.
| this.configuration.put(ClientConfigProperties.ACCESS_TOKEN.getKey(), accessToken); | |
| this.configuration.remove(ClientConfigProperties.BEARERTOKEN_AUTH.getKey()); | |
| this.httpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); | |
| this.configuration.remove(ClientConfigProperties.BEARERTOKEN_AUTH.getKey()); | |
| if (accessToken == null || accessToken.trim().isEmpty()) { | |
| this.configuration.remove(ClientConfigProperties.ACCESS_TOKEN.getKey()); | |
| this.httpHeaders.remove(HttpHeaders.AUTHORIZATION); | |
| } else { | |
| this.configuration.put(ClientConfigProperties.ACCESS_TOKEN.getKey(), accessToken); | |
| this.httpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); | |
| } |
| /** | ||
| * Updates the credentials used for subsequent requests. | ||
| * | ||
| * <p>This method is not thread-safe with respect to other credential updates | ||
| * or concurrent request execution. Applications must coordinate access if | ||
| * they require stronger consistency. | ||
| * |
There was a problem hiding this comment.
The class-level javadoc states Client is thread-safe, but the newly added runtime-auth APIs explicitly state they are not thread-safe with concurrent request execution. Either make these credential updates thread-safe (e.g., by synchronizing updates + reads or using an immutable/atomic snapshot) or update the Client thread-safety claim so it remains accurate for callers.
| CLIENT_NETWORK_BUFFER_SIZE("client_network_buffer_size", Integer.class, "300000"), | ||
|
|
||
| /** | ||
| * Preferred client setting for token-based authentication like JWT and Oauth. |
There was a problem hiding this comment.
Doc comment terminology: "Oauth" should be "OAuth".
| * Preferred client setting for token-based authentication like JWT and Oauth. | |
| * Preferred client setting for token-based authentication like JWT and OAuth. |
| * | ||
| * @param bearer - token to use | ||
| */ |
There was a problem hiding this comment.
updateBearerToken() is now documented as a legacy alias for setAccessToken(), but it isn't marked @Deprecated (unlike Builder.useBearerTokenAuth() and the BEARERTOKEN_AUTH config key doc). Marking this method as @Deprecated would better signal the intended migration path for API consumers.
| * | |
| * @param bearer - token to use | |
| */ | |
| * | |
| * @deprecated Use {@link #setAccessToken(String)} instead. | |
| * @param bearer - token to use | |
| */ | |
| @Deprecated |
| boolean customHttpHeaders = configuration.containsKey(AUTHORIZATION_HEADER_KEY); | ||
|
|
||
| if (!(useSslAuth || hasAccessToken || hasUser || hasPassword || customHttpHeaders)) { | ||
| throw new ClientMisconfigurationException("Username and password (or access token or SSL authentication or pre-define Authorization header) are required"); |
There was a problem hiding this comment.
Error message grammar: "pre-define Authorization header" should be "pre-defined Authorization header".
| throw new ClientMisconfigurationException("Username and password (or access token or SSL authentication or pre-define Authorization header) are required"); | |
| throw new ClientMisconfigurationException("Username and password (or access token or SSL authentication or pre-defined Authorization header) are required"); |
| boolean customHttpHeaders = configuration.containsKey(AUTHORIZATION_HEADER_KEY); | ||
|
|
||
| if (!(useSslAuth || hasAccessToken || hasUser || hasPassword || customHttpHeaders)) { | ||
| throw new ClientMisconfigurationException("Username and password (or access token or SSL authentication or pre-define Authorization header) are required"); |
There was a problem hiding this comment.
The thrown message says "Username and password ... are required", but the validation allows config with only one of USER or PASSWORD (and also allows only Authorization header). Consider rewording to avoid implying both are mandatory, and to reflect the accepted auth modes more clearly.
| throw new ClientMisconfigurationException("Username and password (or access token or SSL authentication or pre-define Authorization header) are required"); | |
| throw new ClientMisconfigurationException( | |
| "Authentication configuration is required: provide username or password, access token, SSL authentication, or a pre-defined Authorization header"); |
|
|
||
| /** | ||
| * Preferred client setting for token-based authentication like JWT and Oauth. | ||
| * For Http it is translated to Authorization Bearer header. |
There was a problem hiding this comment.
Doc comment casing: "For Http" should be "For HTTP".
| * For Http it is translated to Authorization Bearer header. | |
| * For HTTP it is translated to Authorization Bearer header. |
| createUser(adminClient, firstUser, firstPassword); | ||
| createUser(adminClient, secondUser, secondPassword); | ||
|
|
||
| // Create a client with the first user. (It is recommended to use non-existing users for security reasons) |
There was a problem hiding this comment.
Comment says "recommended to use non-existing users" but the example just created the users with CREATE USER IF NOT EXISTS. This looks misleading—consider rewording to something like "use unique/ephemeral users" (or remove the sentence) so the guidance matches the example behavior.
| // Create a client with the first user. (It is recommended to use non-existing users for security reasons) | |
| // Create a client with the first user. Using unique, ephemeral demo users helps avoid reusing existing accounts. |



Summary
Closes #2801
Checklist
Delete items not relevant to your PR: